﻿#region

using System;
using System.Collections.Generic;
using System.Linq;
using Diagnostics.Annotations;
using MobilePal.Collections;
using MobilePal.Log;
using MobilePal.Rules;
using MobilePal.State;

#endregion

namespace MobilePal.ProcessorAgents
{
    public class RuleAgent : IProcessorAgent
    {
        private ActivityCollection _activityCollectionSource;
        private ActivityRule _activityRule;

        #region Methods

        public void AttachToState(List<ILogger> loggers, [NotNull]ApplicationState applicationState)
        {
            _activityCollectionSource = applicationState.Activities;

            applicationState.Activities.CollectionChanged += ActivitiesCollectionChanged;
            applicationState.Activities.ActivityCategoriesChanged += CategoriesCollectionChanged;
            applicationState.Categories.CollectionChanged += CategoriesCollectionChanged;

            AttachToRule(_activityRule);
        }

        public void DetachFromState(ApplicationState applicationState)
        {
            DetachFromRule(_activityRule);

            applicationState.Activities.CollectionChanged -= ActivitiesCollectionChanged;
            applicationState.Activities.ActivityCategoriesChanged -= CategoriesCollectionChanged;
            applicationState.Categories.CollectionChanged -= CategoriesCollectionChanged;
        }

        #endregion

        [NotNull]
        public ActivityRule ActivityRule
        {
            get 
            { 
                return _activityRule;
            }
            set 
            {
                DetachFromRule(_activityRule);
                _activityRule = value;                               
                RebuildProcessedActivities();
                AttachToRule(_activityRule);
            }
        }


        private void AttachToRule(ActivityRule activityRule)
        {
            if (activityRule == null)
                return;

            activityRule.Activities.CollectionChanged += RuleActivitiesCollectionChanged;
            activityRule.Categories.CollectionChanged += RuleCategoriesCollectionChanged;
            activityRule.ExcludedActivities.CollectionChanged += RuleActivitiesCollectionChanged;
            activityRule.ExcludedCategories.CollectionChanged += RuleCategoriesCollectionChanged;
        }

        private void DetachFromRule(ActivityRule activityRule)
        {
            if (activityRule == null)
                return;

            activityRule.Activities.CollectionChanged -= RuleActivitiesCollectionChanged;
            activityRule.Categories.CollectionChanged -= RuleCategoriesCollectionChanged;
            activityRule.ExcludedActivities.CollectionChanged -= RuleActivitiesCollectionChanged;
            activityRule.ExcludedCategories.CollectionChanged -= RuleCategoriesCollectionChanged;
        }

        private void RuleActivitiesCollectionChanged(object sender, NotifyCollectionChangedEventArgs<Activity> e)
        {
            RebuildProcessedActivities();
        }

        private void RuleCategoriesCollectionChanged(object sender, NotifyCollectionChangedEventArgs<ActivityCategory> e)
        {
            RebuildProcessedActivities();
        }

        private void ActivitiesCollectionChanged(object sender, NotifyCollectionChangedEventArgs<Activity> e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    OnActivitiesAdded(e.NewItems);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    OnActivitiesRemoved(e.OldItems);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    OnActivitiesRemoved(e.OldItems);
                    OnActivitiesAdded(e.NewItems);
                    break;
            }

            RebuildProcessedActivities();
        }

        private void OnActivitiesAdded(IEnumerable<Activity> newItems)
        {
        }

        private void OnActivitiesRemoved(IEnumerable<Activity> oldItems)
        {
            foreach (var oldItem in oldItems)
            {
                if (_activityRule.Activities.Contains(oldItem))
                    _activityRule.Activities.Remove(oldItem);

                if (_activityRule.ExcludedActivities.Contains(oldItem))
                    _activityRule.ExcludedActivities.Remove(oldItem);
            }
        }

        private void CategoriesCollectionChanged(object sender, NotifyCollectionChangedEventArgs<ActivityCategory> e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    OnCategoriesAdded(e.NewItems);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    OnCategoriesRemoved(e.OldItems);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    OnCategoriesRemoved(e.OldItems);
                    OnCategoriesAdded(e.NewItems);
                    break;
            }

            RebuildProcessedActivities();
        }

        private void OnCategoriesAdded(IEnumerable<ActivityCategory> newItems)
        {
        }

        private void OnCategoriesRemoved(IEnumerable<ActivityCategory> oldItems)
        {
            foreach (var oldItem in oldItems)
            {
                if (_activityRule.Categories.Contains(oldItem))
                    _activityRule.Categories.Remove(oldItem);

                if (_activityRule.ExcludedCategories.Contains(oldItem))
                    _activityRule.ExcludedCategories.Remove(oldItem);
            }
        }

        private void RebuildProcessedActivities()
        {
            if (_activityCollectionSource == null)
                return;

            var newProcessedActivities = new List<Activity>();

            foreach (Activity activity in _activityRule.Activities)
            {
                if (!newProcessedActivities.Contains(activity))
                    newProcessedActivities.Add(activity);
            }

            foreach (ActivityCategory category in _activityRule.Categories)
            {
                foreach (Activity activity in  GetActivities(category))
                {
                    if (!newProcessedActivities.Contains(activity))
                        newProcessedActivities.Add(activity);
                }
            }

            foreach (Activity activity in _activityRule.ExcludedActivities)
            {
                if (newProcessedActivities.Contains(activity))
                    newProcessedActivities.Remove(activity);
            }

            foreach (ActivityCategory category in _activityRule.ExcludedCategories)
            {
                foreach (var activity in  GetActivities(category))
                {
                    if (newProcessedActivities.Contains(activity))
                        newProcessedActivities.Remove(activity);
                }
            }

            _activityRule.ProcessedActivities.Clear();
            _activityRule.ProcessedActivities.AddRange(newProcessedActivities);
        }

        private IEnumerable<Activity> GetActivities(ActivityCategory category)
        {
            ActivityCategory ruleCategory = category;
            return _activityCollectionSource.Where(activity => activity.Categories.Contains(ruleCategory));
        }
    }
}